#!/usr/bin/perl -w
#
# $Id: RateGraph.pm,v 1.5 2003/07/17 19:37:13 solovam Exp $
#
# This file is a part of gkismet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

#
# RateGraph class
#
package RateGraph;

use Gnome;
use Misc;
use TransientConnObserver;
use DrawingArea;
use strict;
@RateGraph::ISA = qw(TransientConnObserver);

my $width = 300;
my $height = 200;
my $barWidth = 1;
my $pegLen = 5;
# We'd rather have more points than needed than fewer
my $maxPoints = $width / $barWidth + 1;

#
# Constructor
#
sub new
{
	my $type = shift;
	my $self = new TransientConnObserver(@_);
	bless $self, $type;

	# Init data
	$self->{'data'} = [];

        # Window
	my $window = new Gtk::Window('dialog');
	$window->signal_connect("delete_event", sub {$self->deleteHandler()});
	$window->set_title('Packet rate');
	$self->{'window'} = $window;

	# Drawing area
	# Calculate vert and horizontal padding (%.5g format WILL fit into the this width)
	$self->{'hPad'} = DrawingArea->stringWidth($window, '00000') + $pegLen * 3;
	$self->{'vPad'} = DrawingArea->stringHeight($window, '0') * 2 + $pegLen * 3;
	$self->{'drawingArea'} = new DrawingArea($width + 2 * $self->{'hPad'} + 1, $height + 2 * $self->{'vPad'} + 1, sub {$self->drawPlot()});
	my $frame = new Gtk::Frame(undef);
	$frame->add($self->{'drawingArea'}->getWidget());
	my $fixed = new Gtk::Fixed();
	$fixed->put($frame, 0, 0);
	my $hbox= new Gtk::HBox($true, 0);
	$hbox->pack_start($fixed, $true, $false, 20);

	# Button
	my $bbox = new Gtk::HButtonBox();
	$bbox->set_layout('spread');
	$bbox->set_spacing(5);
	my $buttonClose = new Gtk::Button("Close");
	$buttonClose->signal_connect( "clicked", sub {$self->closeClicked()});
	$bbox->add($buttonClose);

	# Assemble
	my $vbox = new Gtk::VBox($false, 10);
	$vbox->pack_start($hbox, $true, $false, 20);
        $vbox->pack_start($bbox, $true, $false, 20);
        $window->add($vbox);
        $window->show_all();

	# Get updates
	$self->{'connection'}->addObserver($self);

	return $self;
}

#
# Draw the plot. A bit ugly. I tried, but...
#
sub drawPlot
{
	my $self = shift;

	# Abbreviations
	my $pixmap = $self->{'drawingArea'}->pixmap();
	my $gtkDrawingArea = $self->{'drawingArea'}->gtkDrawingArea();

	# Backdrop
	$pixmap->draw_rectangle($gtkDrawingArea->style()->black_gc(), $true, 0, 0, $gtkDrawingArea->allocation()->[2], $gtkDrawingArea->allocation()->[3]);

	# Paddings
	my $hPad = $self->{'hPad'};
	my $vPad = $self->{'vPad'};

	# Horizontal axis
	$pixmap->draw_line($gtkDrawingArea->style()->white_gc(), $hPad - $pegLen, $vPad + $height, $hPad + $width, $vPad + $height);
	for(my $l = 0, my $i = 0; $l <= $width; $i++, $l = $barWidth * 60 * $i)
	{
		$pixmap->draw_line($gtkDrawingArea->style()->white_gc(),
			$hPad + $width - $l, $vPad + $height, $hPad + $width - $l, $vPad + $height + $pegLen);
		$pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
                        $hPad + $width - $l - $self->{'drawingArea'}->stringWidth($i) / 2,
			$vPad + $height + $pegLen * 2 + $self->{'drawingArea'}->stringHeight($i), $i);
	}
	$pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
		$hPad/2 - $self->{'drawingArea'}->stringWidth('min') / 2,
		$vPad + $height + $pegLen * 2 + $self->{'drawingArea'}->stringHeight('min'), 'min');

	# Vertical axis
	$pixmap->draw_line($gtkDrawingArea->style()->white_gc(), $hPad + $width, $vPad - $pegLen, $hPad + $width, $vPad + $height);
	my $maxRate = $self->maxRate();
	my $step = Misc->pow(10, int(Misc->log10($maxRate)));
	for(my $i = 0, my $l = 0; int($l) <= $height; $i++, $l = $step * $i * $height / $maxRate)
        {
                $pixmap->draw_line($gtkDrawingArea->style()->white_gc(),
                        $hPad + $width, $vPad + $height - $l, $hPad + $width + $pegLen, $vPad + $height - $l);
                $pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
                        $hPad + $width + $pegLen * 2, $vPad + $height - $l + $self->{'drawingArea'}->stringHeight($step * $i) / 2,
			sprintf('%.5g', $step * $i));
        }
	$pixmap->draw_string($gtkDrawingArea->style()->font(), $gtkDrawingArea->style()->white_gc(),
		$hPad + $width + $pegLen * 2, $vPad / 2 + $self->{'drawingArea'}->stringHeight('Pkt/s') / 2, 'Pkt/s');

	# Plot
	my $now = time();
	my $prev = $now;
	for(my $i = $#{$self->{'data'}}; $i >=0 && ($now - $self->{'data'}[$i]{'time'}) * $barWidth <= $width; $i--)
	{
		$pixmap->draw_rectangle($self->{'drawingArea'}->{'redGC'}, $true,
			$hPad + $width - ($now - $self->{'data'}[$i]{'time'}) * $barWidth,
			$vPad + $height - int($self->{'data'}[$i]{'rate'} * $height / $maxRate),
			($prev - $self->{'data'}[$i]{'time'}) * $barWidth,
			int($self->{'data'}[$i]{'rate'} * $height / $maxRate));
		$prev = $self->{'data'}[$i]{'time'};
	}

	$gtkDrawingArea->draw();
}

#
# Max packet rate
#
sub maxRate
{
	my $self = shift;
	my $max = 1;
	for (@{$self->{'data'}})
	{
		if($_->{'rate'} > $max)
		{
			$max = $_->{'rate'};
		}
	}
	return $max;
}

#
# Return widget
#
sub getWidget
{
	my $self = shift;
	return $self->{'window'};
}

#
# Handle an update from an observable
#
sub update
{
	my $self = shift;
	my $object = shift;
	my $data = shift;

	if($object->isa('Connection') && defined $self->{'connection'} && $self->{'connection'} eq $object)
	{
		if($data->{'changed'} eq 'info')
                {
			my $info = $self->{'connection'}->getInfo();
			push @{$self->{'data'}}, {rate => $info->{'rate'}, time => time()};
			if($#{$self->{'data'}} >= $maxPoints)
			{
				shift @{$self->{'data'}};
			}
                        $self->drawPlot();
                }
        }
}

1;
